#include <stdlib.h>
#include <limits.h>

#include "imbus_internal.h"
#include "im_unicode.h"

#define UTF8_COMPUTE(Char, Mask, Len)           \
    if (Char < 128) {                           \
        Len = 1;                                \
        Mask = 0x7f;                            \
    } else if ((Char & 0xe0) == 0xc0) {         \
        Len = 2;                                \
        Mask = 0x1f;                            \
    } else if ((Char & 0xf0) == 0xe0) {         \
        Len = 3;                                \
        Mask = 0x0f;                            \
    } else if ((Char & 0xf8) == 0xf0) {         \
        Len = 4;                                \
        Mask = 0x07;                            \
    } else if ((Char & 0xfc) == 0xf8) {         \
        Len = 5;                                \
        Mask = 0x03;                            \
    } else if ((Char & 0xfe) == 0xfc) {         \
        Len = 6;                                \
        Mask = 0x01;                            \
    } else Len = -1;

#define UTF8_LENGTH(Char)                       \
    ((Char) < 0x80 ? 1 :                        \
     ((Char) < 0x800 ? 2 :                      \
      ((Char) < 0x10000 ? 3 :                   \
       ((Char) < 0x200000 ? 4 :                 \
        ((Char) < 0x4000000 ? 5 : 6)))))

#define UTF16_LENGTH(Char)      ((Char) < 0x10000 ? 1 : 2)

#define UTF8_GET(Result, Chars, Count, Mask, Len)   \
    (Result) = (Chars)[0] & (Mask);                 \
    for ((Count) = 1; (Count) < (Len); ++(Count)) { \
        if (((Chars)[(Count)] & 0xc0) != 0x80) {    \
            (Result) = IM_UNICHAR_INVALID;          \
            break;                                  \
        }                                           \
        (Result) <<= 6;                             \
        (Result) |= ((Chars)[(Count)] & 0x3f);      \
    }

#define UNICODE_VALID(Char)                     \
    ((Char) < 0x110000 &&                       \
     (((Char) & 0xFFFFF800) != 0xD800) &&       \
     ((Char) < 0xFDD0 || (Char) > 0xFDEF) &&    \
     ((Char) & 0xFFFE) != 0xFFFE)


static const IMChar utf8_skip_data[256] = {
  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
  3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,1,1
};


const IMChar *const im_utf8_skip = utf8_skip_data;

IMUniChar
im_utf8_get_char (const IMChar *src)
{
    IMUniChar result;
    im_unichar_from_utf8 (&result, src, -1);
    return result;
}
IMSize
im_utf8_strlen (const IMChar *src, IMInt maxlen)
{
    IMSize len = 0;
    const IMChar *start = src;

    _im_return_val_if_fail (src != NULL || maxlen == 0, 0);

    if (maxlen < 0) {
        while (*src) {
          src = im_utf8_next_char (src);
          ++len;
        }
    } else {
        if (maxlen == 0 || !*src)
            return 0;

        src = im_utf8_next_char (src);          

        while (src - start < maxlen && *src) {
            ++len;
            src = im_utf8_next_char (src);
        }

        /* only do the last len increment if we got a complete
         * char (don't count partial chars)
         */
        if (src - start <= maxlen)
            ++len;
    }

    return len;
}

#define CONTINUATION_CHAR                                   \
    do {                                                    \
        if ((*(IMUChar *)p & 0xc0) != 0x80) /* 10xxxxxx */   \
        goto error;                                         \
        val <<= 6;                                          \
        val |= (*(IMUChar *)p) & 0x3f;                       \
    } while (0) 

static const IMChar*
fast_validate_utf8 (const IMChar *str)

{
    IMUniChar val = 0;
    IMUniChar min = 0;
    const IMChar *p;

    for (p = str; *p; ++p) {
        if (*(IMUChar *)p < 128)
            /* done */;
        else {
            const IMChar *last;
          
            last = p;
            if ((*(IMUChar*)p & 0xe0) == 0xc0) { /* 110xxxxx */
                if ((*(IMUChar*)p & 0x1e) == 0)
                    goto error;
                p++;
                if ((*(IMUChar*)p & 0xc0) != 0x80) /* 10xxxxxx */
                    goto error;
            } else {
                if ((*(IMUChar *)p & 0xf0) == 0xe0) { /* 1110xxxx */
                    min = (1 << 11);
                    val = *(IMUChar *)p & 0x0f;
                    goto TWO_REMAINING;
                } else if ((*(IMUChar *)p & 0xf8) == 0xf0) { /* 11110xxx */
                    min = (1 << 16);
                    val = *(IMUChar *)p & 0x07;
                } else
                    goto error;

                p++;
                CONTINUATION_CHAR;
                TWO_REMAINING:
                p++;
                CONTINUATION_CHAR;
                p++;
                CONTINUATION_CHAR;

                if (val < min)
                    goto error;

                if (!UNICODE_VALID(val))
                    goto error;
            }
          
            continue;
          
            error:
            return last;
        }
    }

    return p;
}

static const IMChar *
fast_validate_utf8_len (const IMChar *str, IMInt max_len)
{
    IMUniChar val = 0;
    IMUniChar min = 0;
    const IMChar *p;

    for (p = str; (max_len < 0 || (p - str) < max_len) && *p; ++p) {
        if (*(IMUChar *)p < 128)
            /* done */;
        else {
            const IMChar *last;
          
            last = p;
            if ((*(IMUChar *)p & 0xe0) == 0xc0) { /* 110xxxxx */
                if (max_len >= 0 && max_len - (p - str) < 2)
                    goto error;
              
                if ((*(IMUChar *)p & 0x1e) == 0)
                    goto error;
                p++;
                if ((*(IMUChar *)p & 0xc0) != 0x80) /* 10xxxxxx */
                    goto error;
            } else {
                if ((*(IMUChar *)p & 0xf0) == 0xe0) { /* 1110xxxx */
                    if (max_len >= 0 && max_len - (p - str) < 3)
                        goto error;

                    min = (1 << 11);
                    val = *(IMUChar *)p & 0x0f;
                    goto TWO_REMAINING;
                } else if ((*(IMUChar *)p & 0xf8) == 0xf0) { /* 11110xxx */
                    if (max_len >= 0 && max_len - (p - str) < 4)
                        goto error;

                    min = (1 << 16);
                    val = *(IMUChar *)p & 0x07;
                } else
                    goto error;

                p++;
                CONTINUATION_CHAR;
                TWO_REMAINING:
                p++;
                CONTINUATION_CHAR;
                p++;
                CONTINUATION_CHAR;

                if (val < min)
                    goto error;
                if (!UNICODE_VALID(val))
                    goto error;
            }

            continue;

            error:
            return last;
        }
    }

    return p;
}

IMBool
im_utf8_string_validate (const IMChar *str, IMInt maxlen, const IMChar **end)
{
    const IMChar *p;

    _im_return_val_if_fail (str != 0 && maxlen != 0, FALSE);

    if (maxlen < 0)
        p = fast_validate_utf8 (str);
    else
        p = fast_validate_utf8_len (str, maxlen);

    if (end)
        *end = p;

    if ((maxlen >= 0 && p != str + maxlen) || (maxlen < 0 && *p != '\0'))
        return FALSE;
    else
        return TRUE;
}
IMUniChar
im_utf16_get_char (const IMUInt16 *src)
{
    IMUniChar result;
    im_unichar_from_utf16 (&result, src, -1);
    return result;
}
IMSize
im_utf16_strlen (const IMUInt16 *src, IMInt maxlen)
{
    IMSize len = 0;
    const IMUInt16 *start = src;

    _im_return_val_if_fail (src != 0 || maxlen == 0, 0);

    if (maxlen < 0) {
        while (*src) {
            src += im_utf16_char_length (src);
            ++len;
        }
    } else {
        if (maxlen == 0 || !*src)
            return 0;

        src += im_utf16_char_length (src);

        while (*src && src - start < maxlen) {
            ++len;
            src += im_utf16_char_length (src);
        }

        /* only do the last len increment if we got a complete
         * char (don't count partial chars)
         */
        if (src - start <= maxlen)
            ++len;
    }

    return len;
}
IMBool
im_utf16_string_validate (const IMUInt16 *str, IMInt maxlen, const IMUInt16 **end)
{
    const IMUInt16 *p;

    for (p = str; (maxlen < 0 || (p - str) < maxlen) && *p; ++p) {
        if (*p == 0xffff || *p == 0xfffe)
            /* Invalid sequence */
            break;
        else if (*p < 0xd800 || *p >= 0xe000)
            /* done */;
        else if ((*p >= 0xdc00 && *p < 0xe000))
            /* Invalid sequence */
            break;
        else if (*p >= 0xd800 && *p < 0xdc00) {
            if ((maxlen < 0 || (p - str) < (maxlen - 1)) && *(p+1) >= 0xdc00 && *(p+1) < 0xe000)
                ++p;
            else /* Invalid sequence */
                break;
        }
    }

    if (end) *end = p;

    if ((maxlen >= 0 && p != str + maxlen) || (maxlen < 0 && *p != 0))
        return FALSE;
    else
        return TRUE;
}
IMSize
im_unichar_from_utf8 (IMUniChar *dest, const IMChar *src, IMInt src_len)
{
    int i, mask = 0, len;
    IMUniChar result;
    unsigned char c;
 
    _im_return_val_if_fail (src != 0 || src_len == 0, 0);

    if (src_len == 0 || !*src)
        return 0;

    c = (unsigned char) *src;

    UTF8_COMPUTE (c, mask, len);

    if (len == -1 || (src_len > 0 && len > src_len)) {
        if (dest) *dest = IM_UNICHAR_INVALID;
        return 0;
    }

    UTF8_GET (result, src, i, mask, len);

    if (dest) *dest = result;

    return len;
}

IMSize
im_unichar_to_utf8 (IMChar *dest, IMUniChar src, IMSize dest_size)
{
    IMSize count;

    _im_return_val_if_fail (dest != 0, 0);
    _im_return_val_if_fail (dest_size != 0, 0);

    count = UTF8_LENGTH(src);

    if (dest_size < count)
        return 0;
    switch (count) { /* note: code falls through cases! */
        case 6: dest [5] = 0x80 | (src & 0x3f); src = src >> 6; src |= 0x4000000;
        case 5: dest [4] = 0x80 | (src & 0x3f); src = src >> 6; src |= 0x200000;
        case 4: dest [3] = 0x80 | (src & 0x3f); src = src >> 6; src |= 0x10000;
        case 3: dest [2] = 0x80 | (src & 0x3f); src = src >> 6; src |= 0x800;
        case 2: dest [1] = 0x80 | (src & 0x3f); src = src >> 6; src |= 0xc0;
        case 1: dest [0] = src;
    }
    return count;
}

#define SURROGATE_VALUE(h,l) (((h) - 0xd800) * 0x400 + (l) - 0xdc00 + 0x10000)
IMSize
im_unichar_from_utf16 (IMUniChar *dest, const IMUInt16 *src, IMInt src_len)
{
    IMUniChar c;

    _im_return_val_if_fail (src != 0 || src_len == 0, 0);

    if (src_len == 0 || !*src)
        return 0;

    c = *src;
    if (c >= 0xd800 && c < 0xdc00) {
        if (src_len > 1 || src_len == -1) {
            IMUniChar l = *(src+1);
            if (l >= 0xdc00 && l < 0xe000) {
                if (dest) *dest = SURROGATE_VALUE(c,l);
                return 2;
            }
        }
        if (dest) *dest = IM_UNICHAR_INVALID;
        return 0;
    } else if (c >= 0xdc00 && c < 0xe000) {
        if (dest) *dest = IM_UNICHAR_INVALID;
        return 0;
    }

    if (dest) *dest = c;

    return 1;
}

IMSize
im_unichar_to_utf16 (IMUInt16 *dest, IMUniChar src, IMSize dest_size)
{
    _im_return_val_if_fail (dest != 0, 0);
    _im_return_val_if_fail (dest_size != 0, 0);

    if (!UNICODE_VALID(src))
        return 0;

    if (src < 0x10000) {
        *dest = (IMUInt16) src;
        return 1;
    } else if (dest_size > 1) {
        *(dest++) = (src - 0x10000) / 0x400 + 0xd800;
        *dest     = (src - 0x10000) % 0x400 + 0xdc00;
        return 2;
    }

    return 0;
}
IMBool
im_unichar_validate (IMUniChar ch)
{
    return UNICODE_VALID (ch);
}
IMSize
im_utf8_string_to_ucs4 (IMUniChar *dest, IMSize dest_size, const IMChar *src, IMInt src_len)
{
    const IMChar *p;
    IMUniChar ch;
    IMSize count = 0;
    IMSize len;

    _im_return_val_if_fail (dest != 0, 0);
    _im_return_val_if_fail (dest_size != 0, 0);
    _im_return_val_if_fail (src != 0, 0);
    _im_return_val_if_fail (src_len != 0, 0);

    p = src;

    while (*p && count < dest_size && (src_len < 0 || (p - src) < src_len)) {
        len = im_unichar_from_utf8 (&ch, p, (src_len < 0) ? -1 : (src_len - (p - src)));
        if (ch == IM_UNICHAR_INVALID || !len)
            break;
        dest[count] = ch;
        p += len;
        ++count;
    }

    if (count < dest_size)
        dest [count] = 0;

    return count;
}
IMSize
im_utf16_string_to_ucs4 (IMUniChar *dest, IMSize dest_size, const IMUInt16 *src, IMInt src_len)
{
    const IMUInt16 *p;
    IMUniChar ch;
    IMSize count = 0;
    IMSize len;

    _im_return_val_if_fail (dest != 0, 0);
    _im_return_val_if_fail (dest_size != 0, 0);
    _im_return_val_if_fail (src != 0, 0);
    _im_return_val_if_fail (src_len != 0, 0);

    p = src;

    while (*p && count < dest_size && (src_len < 0 || (p - src) < src_len)) {
        len = im_unichar_from_utf16 (&ch, p, (src_len < 0) ? -1 : (src_len - (p - src)));
        if (ch == IM_UNICHAR_INVALID || !len)
            break;
        dest[count] = ch;
        p += len;
        ++count;
    }

    if (count < dest_size)
        dest [count] = 0;

    return count;
}
IMSize
im_ucs4_string_to_utf8 (IMChar *dest, IMSize dest_size, const IMUniChar *src, IMInt src_len)
{
    const IMUniChar *p;

    IMSize count = 0;
    IMSize len;

    _im_return_val_if_fail (dest != 0, 0);
    _im_return_val_if_fail (dest_size != 0, 0);
    _im_return_val_if_fail (src != 0, 0);
    _im_return_val_if_fail (src_len != 0, 0);

    p = src;

    while (*p && count < dest_size && (src_len < 0 || (p - src) < src_len)) {
        len = im_unichar_to_utf8 (dest + count, *p, dest_size - count);

        if (!len) break;

        count += len;
        ++p;
    }

    if (count < dest_size)
        dest [count] = 0;

    return count;
}
IMSize
im_ucs4_string_to_utf16 (IMUInt16 *dest, IMSize dest_size, const IMUniChar *src, IMInt src_len)
{
    const IMUniChar *p;

    IMSize count = 0;
    IMSize len;

    _im_return_val_if_fail (dest != 0, 0);
    _im_return_val_if_fail (dest_size != 0, 0);
    _im_return_val_if_fail (src != 0, 0);
    _im_return_val_if_fail (src_len != 0, 0);

    p = src;

    while (*p && count < dest_size && (src_len < 0 || (p - src) < src_len)) {
        len = im_unichar_to_utf16 (dest + count, *p, dest_size - count);

        if (!len) break;

        count += len;
        ++p;
    }

    if (count < dest_size)
        dest [count] = 0;

    return count;
}

IMSize
im_ucs4_strlen (const IMUniChar *str, IMInt maxlen)
{
    IMSize len = 0;
    const IMUniChar *p;

    _im_return_val_if_fail (str != 0 || maxlen == 0, 0);

    if (maxlen == 0 || !*str)
        return 0;

    for (p = str; (maxlen < 0 || (p - str) < maxlen) && *p && UNICODE_VALID(*p); ++p)
        ++len;

    return len;
}

IMSize
im_ucs4_string_utf8_length (const IMUniChar *str, IMInt maxlen)
{
    IMSize len = 0;
    const IMUniChar *p;

    _im_return_val_if_fail (str != 0 || maxlen == 0, 0);

    if (maxlen == 0 || !*str)
        return 0;

    for (p = str; (maxlen < 0 || (p - str) < maxlen) && *p && UNICODE_VALID(*p); ++p)
        len += UTF8_LENGTH(*p);

    return len;
}

IMSize
im_ucs4_string_utf16_length (const IMUniChar *str, IMInt maxlen)
{
    IMSize len = 0;
    const IMUniChar *p;

    _im_return_val_if_fail (str != 0 || maxlen == 0, 0);

    if (maxlen == 0 || !*str)
        return 0;

    for (p = str; (maxlen < 0 || (p - str) < maxlen) && *p && UNICODE_VALID(*p); ++p)
        len += UTF16_LENGTH(*p);

    return len;
}

/*
vi:ts=4:nowrap:ai:expandtab
*/
